{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d2eecb96-cf0e-47ed-8116-88a7eaa4236d",
   "metadata": {},
   "source": [
    "# How to add cross-thread persistence (functional API)\n",
    "\n",
    "!!! info \"Prerequisites\"\n",
    "\n",
    "    This guide assumes familiarity with the following:\n",
    "    \n",
    "    - [Functional API](../../concepts/functional_api/)\n",
    "    - [Persistence](../../concepts/persistence/)\n",
    "    - [Memory](../../concepts/memory/)\n",
    "    - [Chat Models](https://js.langchain.com/docs/concepts/chat_models/)\n",
    "\n",
    "LangGraph allows you to persist data across **different [threads](../../concepts/persistence/#threads)**. For instance, you can store information about users (their names or preferences) in a shared (cross-thread) memory and reuse them in the new threads (e.g., new conversations).\n",
    "\n",
    "When using the [functional API](../../concepts/functional_api/), you can set it up to store and retrieve memories by using the [Store](/langgraphjs/reference/classes/checkpoint.BaseStore.html) interface:\n",
    "\n",
    "1. Create an instance of a `Store`\n",
    "\n",
    "    ```ts\n",
    "    import { InMemoryStore } from \"@langchain/langgraph\";\n",
    "    \n",
    "    const store = new InMemoryStore();\n",
    "    ```\n",
    "\n",
    "2. Pass the `store` instance to the `entrypoint()` wrapper function. It will be passed to the workflow as `config.store`.\n",
    "\n",
    "    ```ts\n",
    "    import { entrypoint } from \"@langchain/langgraph\";\n",
    "    \n",
    "    const workflow = entrypoint({\n",
    "      store,\n",
    "      name: \"myWorkflow\",\n",
    "    }, async (input, config) => {\n",
    "      const foo = await myTask({input, store: config.store});\n",
    "      ...\n",
    "    });\n",
    "    ```\n",
    "    \n",
    "In this guide, we will show how to construct and use a workflow that has a shared memory implemented using the [Store](/langgraphjs/reference/classes/checkpoint.BaseStore.html) interface.\n",
    "\n",
    "!!! tip \"Note\"\n",
    "\n",
    "    If you need to add cross-thread persistence to a `StateGraph`, check out this [how-to guide](../cross-thread-persistence)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "153fceff",
   "metadata": {},
   "source": [
    "## Setup\n",
    "\n",
    "!!! note Compatibility\n",
    "\n",
    "    This guide requires `@langchain/langgraph>=0.2.42`.\n",
    "\n",
    "First, install the required dependencies for this example:\n",
    "\n",
    "```bash\n",
    "npm install @langchain/langgraph @langchain/openai @langchain/anthropic @langchain/core uuid\n",
    "```\n",
    "\n",
    "Next, we need to set API keys for Anthropic and OpenAI (the LLM and embeddings we will use):\n",
    "\n",
    "```typescript\n",
    "process.env.OPENAI_API_KEY = \"YOUR_API_KEY\";\n",
    "process.env.ANTHROPIC_API_KEY = \"YOUR_API_KEY\";\n",
    "```\n",
    "\n",
    "!!! tip \"Set up [LangSmith](https://smith.langchain.com) for LangGraph development\"\n",
    "\n",
    "    Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b5b3d42-3d2c-455e-ac10-e2ae74dc1cf1",
   "metadata": {},
   "source": [
    "## Example: simple chatbot with long-term memory"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4c550b5-1954-496b-8b9d-800361af17dc",
   "metadata": {},
   "source": [
    "### Define store\n",
    "\n",
    "In this example we will create a workflow that will be able to retrieve information about a user's preferences. We will do so by defining an `InMemoryStore` - an object that can store data in memory and query that data.\n",
    "\n",
    "When storing objects using the `Store` interface you define two things:\n",
    "\n",
    "* the namespace for the object, a tuple (similar to directories)\n",
    "* the object key (similar to filenames)\n",
    "\n",
    "In our example, we'll be using `[\"memories\", <user_id>]` as namespace and random UUID as key for each new memory.\n",
    "\n",
    "Let's first define our store:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a7f303d6-612e-4e34-bf36-29d4ed25d802",
   "metadata": {},
   "outputs": [],
   "source": [
    "import { InMemoryStore } from \"@langchain/langgraph\";\n",
    "import { OpenAIEmbeddings } from \"@langchain/openai\";\n",
    "\n",
    "const inMemoryStore = new InMemoryStore({\n",
    "  index: {\n",
    "    embeddings: new OpenAIEmbeddings({\n",
    "      model: \"text-embedding-3-small\",\n",
    "    }),\n",
    "    dims: 1536,\n",
    "  },\n",
    "});"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3389c9f4-226d-40c7-8bfc-ee8aac24f79d",
   "metadata": {},
   "source": [
    "### Create workflow\n",
    "\n",
    "Now let's create our workflow:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2a30a362-528c-45ee-9df6-630d2d843588",
   "metadata": {},
   "outputs": [],
   "source": [
    "import { v4 } from \"uuid\";\n",
    "import { ChatAnthropic } from \"@langchain/anthropic\";\n",
    "import {\n",
    "  entrypoint,\n",
    "  task,\n",
    "  MemorySaver,\n",
    "  addMessages,\n",
    "  type BaseStore,\n",
    "  getStore,\n",
    "} from \"@langchain/langgraph\";\n",
    "import type { BaseMessage, BaseMessageLike } from \"@langchain/core/messages\";\n",
    "\n",
    "const model = new ChatAnthropic({\n",
    "  model: \"claude-3-5-sonnet-latest\",\n",
    "});\n",
    "\n",
    "const callModel = task(\"callModel\", async (\n",
    "  messages: BaseMessage[],\n",
    "  memoryStore: BaseStore,\n",
    "  userId: string\n",
    ") => {\n",
    "  const namespace = [\"memories\", userId];\n",
    "  const lastMessage = messages.at(-1);\n",
    "  if (typeof lastMessage?.content !== \"string\") {\n",
    "    throw new Error(\"Received non-string message content.\");\n",
    "  }\n",
    "  const memories = await memoryStore.search(namespace, {\n",
    "    query: lastMessage.content,\n",
    "  });\n",
    "  const info = memories.map((memory) => memory.value.data).join(\"\\n\");\n",
    "  const systemMessage = `You are a helpful assistant talking to the user. User info: ${info}`;\n",
    "  \n",
    "  // Store new memories if the user asks the model to remember\n",
    "  if (lastMessage.content.toLowerCase().includes(\"remember\")) {\n",
    "    // Hard-coded for demo\n",
    "    const memory = `Username is Bob`;\n",
    "    await memoryStore.put(namespace, v4(), { data: memory });\n",
    "  }\n",
    "  const response = await model.invoke([\n",
    "    {\n",
    "      role: \"system\",\n",
    "      content: systemMessage \n",
    "    },\n",
    "    ...messages\n",
    "  ]);\n",
    "  return response;\n",
    "});\n",
    "\n",
    "// NOTE: we're passing the store object here when creating a workflow via entrypoint()\n",
    "const workflow = entrypoint({\n",
    "  checkpointer: new MemorySaver(),\n",
    "  store: inMemoryStore,\n",
    "  name: \"workflow\",\n",
    "}, async (params: {\n",
    "  messages: BaseMessageLike[];\n",
    "  userId: string;\n",
    "}, config) => {\n",
    "  const messages = addMessages([], params.messages)\n",
    "  const response = await callModel(messages, config.store, params.userId);\n",
    "  return entrypoint.final({\n",
    "    value: response,\n",
    "    save: addMessages(messages, response),\n",
    "  });\n",
    "});"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f22a4a18-67e4-4f0b-b655-a29bbe202e1c",
   "metadata": {},
   "source": [
    "The current store is passed in as part of the entrypoint's second argument, as `config.store`.\n",
    "\n",
    "!!! note Note\n",
    "\n",
    "    If you're using LangGraph Cloud or LangGraph Studio, you __don't need__ to pass store into the entrypoint, since it's done automatically."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "552d4e33-556d-4fa5-8094-2a076bc21529",
   "metadata": {},
   "source": [
    "### Run the workflow!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1842c626-6cd9-4f58-b549-58978e478098",
   "metadata": {},
   "source": [
    "Now let's specify a user ID in the config and tell the model our name:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c871a073-a466-46ad-aafe-2b870831057e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"msg_01U4xHvf4REPSCGWzpLeh1qJ\",\n",
      "  \"content\": \"Hi Bob! Nice to meet you. I'll remember that your name is Bob. How can I help you today?\",\n",
      "  \"additional_kwargs\": {\n",
      "    \"id\": \"msg_01U4xHvf4REPSCGWzpLeh1qJ\",\n",
      "    \"type\": \"message\",\n",
      "    \"role\": \"assistant\",\n",
      "    \"model\": \"claude-3-5-sonnet-20241022\",\n",
      "    \"stop_reason\": \"end_turn\",\n",
      "    \"stop_sequence\": null,\n",
      "    \"usage\": {\n",
      "      \"input_tokens\": 28,\n",
      "      \"cache_creation_input_tokens\": 0,\n",
      "      \"cache_read_input_tokens\": 0,\n",
      "      \"output_tokens\": 27\n",
      "    }\n",
      "  },\n",
      "  \"response_metadata\": {\n",
      "    \"id\": \"msg_01U4xHvf4REPSCGWzpLeh1qJ\",\n",
      "    \"model\": \"claude-3-5-sonnet-20241022\",\n",
      "    \"stop_reason\": \"end_turn\",\n",
      "    \"stop_sequence\": null,\n",
      "    \"usage\": {\n",
      "      \"input_tokens\": 28,\n",
      "      \"cache_creation_input_tokens\": 0,\n",
      "      \"cache_read_input_tokens\": 0,\n",
      "      \"output_tokens\": 27\n",
      "    },\n",
      "    \"type\": \"message\",\n",
      "    \"role\": \"assistant\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"input_tokens\": 28,\n",
      "    \"output_tokens\": 27,\n",
      "    \"total_tokens\": 55,\n",
      "    \"input_token_details\": {\n",
      "      \"cache_creation\": 0,\n",
      "      \"cache_read\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config = {\n",
    "  configurable: {\n",
    "    thread_id: \"1\",\n",
    "  },\n",
    "  streamMode: \"values\" as const,\n",
    "};\n",
    "\n",
    "const inputMessage = {\n",
    "  role: \"user\",\n",
    "  content: \"Hi! Remember: my name is Bob\",\n",
    "};\n",
    "\n",
    "const stream = await workflow.stream({ messages: [inputMessage], userId: \"1\" }, config);\n",
    "\n",
    "for await (const chunk of stream) {\n",
    "  console.log(chunk);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d862be40-1f8a-4057-81c4-b7bf073dc4c1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"msg_01LB4YapkFawBUbpiu3oeWbF\",\n",
      "  \"content\": \"Your name is Bob.\",\n",
      "  \"additional_kwargs\": {\n",
      "    \"id\": \"msg_01LB4YapkFawBUbpiu3oeWbF\",\n",
      "    \"type\": \"message\",\n",
      "    \"role\": \"assistant\",\n",
      "    \"model\": \"claude-3-5-sonnet-20241022\",\n",
      "    \"stop_reason\": \"end_turn\",\n",
      "    \"stop_sequence\": null,\n",
      "    \"usage\": {\n",
      "      \"input_tokens\": 28,\n",
      "      \"cache_creation_input_tokens\": 0,\n",
      "      \"cache_read_input_tokens\": 0,\n",
      "      \"output_tokens\": 8\n",
      "    }\n",
      "  },\n",
      "  \"response_metadata\": {\n",
      "    \"id\": \"msg_01LB4YapkFawBUbpiu3oeWbF\",\n",
      "    \"model\": \"claude-3-5-sonnet-20241022\",\n",
      "    \"stop_reason\": \"end_turn\",\n",
      "    \"stop_sequence\": null,\n",
      "    \"usage\": {\n",
      "      \"input_tokens\": 28,\n",
      "      \"cache_creation_input_tokens\": 0,\n",
      "      \"cache_read_input_tokens\": 0,\n",
      "      \"output_tokens\": 8\n",
      "    },\n",
      "    \"type\": \"message\",\n",
      "    \"role\": \"assistant\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"input_tokens\": 28,\n",
      "    \"output_tokens\": 8,\n",
      "    \"total_tokens\": 36,\n",
      "    \"input_token_details\": {\n",
      "      \"cache_creation\": 0,\n",
      "      \"cache_read\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config2 = {\n",
    "  configurable: {\n",
    "    thread_id: \"2\",\n",
    "  },\n",
    "  streamMode: \"values\" as const,\n",
    "};\n",
    "\n",
    "const followupStream = await workflow.stream({\n",
    "  messages: [{\n",
    "    role: \"user\",\n",
    "    content: \"what is my name?\",\n",
    "  }],\n",
    "  userId: \"1\"\n",
    "}, config2);\n",
    "\n",
    "for await (const chunk of followupStream) {\n",
    "  console.log(chunk);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80fd01ec-f135-4811-8743-daff8daea422",
   "metadata": {},
   "source": [
    "We can now inspect our in-memory store and verify that we have in fact saved the memories for the user:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "76cde493-89cf-4709-a339-207d2b7e9ea7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{ data: 'Username is Bob' }\n"
     ]
    }
   ],
   "source": [
    "const memories = await inMemoryStore.search([\"memories\", \"1\"]);\n",
    "for (const memory of memories) {\n",
    "  console.log(memory.value);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23f5d7eb-af23-4131-b8fd-2a69e74e6e55",
   "metadata": {},
   "source": [
    "Let's now run the workflow for another user to verify that the memories about the first user are self contained:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d362350b-d730-48bd-9652-983812fd7811",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "AIMessage {\n",
      "  \"id\": \"msg_01KK7CweVY4ZdHxU5bPa4skv\",\n",
      "  \"content\": \"I don't have any information about your name. While I aim to be helpful, I can only know what you directly tell me during our conversation.\",\n",
      "  \"additional_kwargs\": {\n",
      "    \"id\": \"msg_01KK7CweVY4ZdHxU5bPa4skv\",\n",
      "    \"type\": \"message\",\n",
      "    \"role\": \"assistant\",\n",
      "    \"model\": \"claude-3-5-sonnet-20241022\",\n",
      "    \"stop_reason\": \"end_turn\",\n",
      "    \"stop_sequence\": null,\n",
      "    \"usage\": {\n",
      "      \"input_tokens\": 25,\n",
      "      \"cache_creation_input_tokens\": 0,\n",
      "      \"cache_read_input_tokens\": 0,\n",
      "      \"output_tokens\": 33\n",
      "    }\n",
      "  },\n",
      "  \"response_metadata\": {\n",
      "    \"id\": \"msg_01KK7CweVY4ZdHxU5bPa4skv\",\n",
      "    \"model\": \"claude-3-5-sonnet-20241022\",\n",
      "    \"stop_reason\": \"end_turn\",\n",
      "    \"stop_sequence\": null,\n",
      "    \"usage\": {\n",
      "      \"input_tokens\": 25,\n",
      "      \"cache_creation_input_tokens\": 0,\n",
      "      \"cache_read_input_tokens\": 0,\n",
      "      \"output_tokens\": 33\n",
      "    },\n",
      "    \"type\": \"message\",\n",
      "    \"role\": \"assistant\"\n",
      "  },\n",
      "  \"tool_calls\": [],\n",
      "  \"invalid_tool_calls\": [],\n",
      "  \"usage_metadata\": {\n",
      "    \"input_tokens\": 25,\n",
      "    \"output_tokens\": 33,\n",
      "    \"total_tokens\": 58,\n",
      "    \"input_token_details\": {\n",
      "      \"cache_creation\": 0,\n",
      "      \"cache_read\": 0\n",
      "    }\n",
      "  }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "const config3 = {\n",
    "  configurable: {\n",
    "    thread_id: \"3\",\n",
    "  },\n",
    "  streamMode: \"values\" as const,\n",
    "};\n",
    "\n",
    "const otherUserStream = await workflow.stream({\n",
    "  messages: [{\n",
    "    role: \"user\",\n",
    "    content: \"what is my name?\",\n",
    "  }],\n",
    "  userId: \"2\"\n",
    "}, config3);\n",
    "\n",
    "for await (const chunk of otherUserStream) {\n",
    "  console.log(chunk);\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "TypeScript",
   "language": "typescript",
   "name": "tslab"
  },
  "language_info": {
   "codemirror_mode": {
    "mode": "typescript",
    "name": "javascript",
    "typescript": true
   },
   "file_extension": ".ts",
   "mimetype": "text/typescript",
   "name": "typescript",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
